

/**
 * DataSource: {
 *  url: string,
 *  filesize: number,
 *  cors: boolean,
 *  withCredentials: boolean
 * }
 */

 import SpeedSampler from './speed-sampler.js'
 import RangeSeekHandler from './range-seek-handler.js'
 import FetchStreamLoader from './fetch-stream-loader.js'
 

class IOController {
    constructor(dataSource, config, extraData) {
        this._config = config
        this._extraData = extraData
        this._stashInitialSize = 1024 * 384 // default initial size: 384KB

        if (
            config.stashInitialSize != undefined &&
            config.stashInitialSize > 0
        ) {
            // apply from config
            this._stashInitialSize = config.stashInitialSize
        }

        this._stashUsed = 0
        this._stashSize = this._stashInitialSize
        this._bufferSize = 1024 * 1024 * 3 // initial size: 3MB
        this._stashByteStart = 0
        this._enableStash = true
        if (config.enableStashBuffer === false) {
            this._enableStash = false
        }

        this._loader = null
        this._loaderClass = null
        this._seekHandler = null

        this._dataSource = dataSource
        this._isWebSocketURL = false
        this._refTotalLength = dataSource.filesize ? dataSource.filesize : null
        this._fullRequestFlag = false
        this._currentRange = null
        this._redirectedURL = null

        this._speedNormalized = 0
        this._speedSampler = new SpeedSampler()
        this._speedNormalizeList = [64, 128, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096]

        this._isEarlyEofReconnecting = false
        
        this._paused = false
        this._resumeFrom = 0

        this._onDataArrival = null
        this._onSeeked = null
        this._onError = null
        this._onComplete = null
        this._onRedirect = null
        this._onRecoveredEarlyEof = null

        this._selectSeekHandler()
        this._selectLoader()
        this._createLoader()
    }

    _selectSeekHandler() {
        let config = this._config
        if (config.seekType === 'range') {
            // 默认值
            this._seekHandler = new RangeSeekHandler(this._config.rangeLoadZeroStart)
        }
    }

    _selectLoader() {
        this._loaderClass = FetchStreamLoader
    }

    _createLoader() {
        this._loader = new this._loaderClass(this._seekHandler, this._config)
        if (this._loader.needStashBuffer === false) {
            this._enableStash = false
        }
        this._loader.onContentLengthKnown = this._onContentLengthKnown.bind(this)
        this._loader.onURLRedirect = this._onURLRedirect.bind(this)
        this._loader.onDataArrival = this._onDataArrival.bind(this)
        this._loader.onComplete = this._onComplete.bind(this)
        this._loader.onError = this._onError.bind(this)
    }

    _onURLRedirect(redirectedURL) {
        this._redirectedURL = redirectedURL
        if (this._onRedirect) {
            this._onRedirect(redirectedURL)
        }
    }

    open(optionalFrom) {
        this._currentRange = {from: 0, to: -1}
        if (this.optionalFrom) {
            this._currentRange.from = optionalFrom
        }

        this._speedSampler.reset()
        if (!optionalFrom) {
            this._fullRequestFlag = true
        }

        this._loader.open(
            this._dataSource,
            Object.assign({}, this._currentRange)
        )
    }

    abort() {
        this._loader.abort()
    }

    pause() {
        // 非worker不处理
    }

    resume() {
        // 非worker不处理
    }

    seek(bytes) {
        this._paused = false
        this._stashUsed = 0
        this._stashByteStart = 0
        this._internalSeek(bytes, true)
    }

    /**
     * When seeking request is from media seeking,
     * unconsumed stash data should be dropped
     * 
     * However, stash data shouldn't be dropped if seeking requested from http reconnection
     * 
     * @dropUnconsumed: Ignore and discard all unconsumed data in stash buffer
     * 
     */
    _internalSeek(bytes, dropUnconsumed) {
        if (this._loader.isWorking()) {
            this._loader.abort()
        }

        // dispatch & flush stash buffer before seek
        this._flushStashBuffer(dropUnconsumed)

        this._loader.destroy()
        this._loader = null

        let requestRange = {from: bytes, to: -1}
        this._currentRange = {from: requestRange.from, to: -1}

        this._speedSampler.reset()
        this._stashSize = this._stashInitialSize
        this._createLoader()
        this._loader.open(this._dataSource, requestRange)

        if (this._onSeeked) {
            this._onSeeked()
        }
    }

    isPaused() {
        return this._paused
    }
    isWorking() {
        return this._loader && this._loader.isWorking() && !this._paused
    }

    get status() {
        return this._loader.status
    }
    get extraData() {
        this._extraData
    }
    set extraData() {
        this._extraData = data
    }

    // prototype: function onDataArrival(chunks: ArrayBuffer, byteStart: number): number
    get onDataArrival() {
        return this._onDataArrival
    }
    set onDataArrival(callback) {
        this._onDataArrival = callback
    }

    get onSeeked() {
        return this._onSeeked
    }
    set onSeeked(callback) {
        this._onSeeked = callback
    }

    // prototype: function onError(type: number, info: {code: number, msg: string}): void
    get onError() {
        return this._onError
    }
    set onError(callback) {
        this._onError = callback
    }

    get onComplete() {
        return this._onComplete;
    }

    set onComplete(callback) {
        this._onComplete = callback;
    }

    get onRedirect() {
        return this._onRedirect;
    }

    set onRedirect(callback) {
        this._onRedirect = callback;
    }

    get onRecoveredEarlyEof() {
        return this._onRecoveredEarlyEof;
    }

    set onRecoveredEarlyEof(callback) {
        this._onRecoveredEarlyEof = callback;
    }

    get currentURL() {
        return this._dataSource.url;
    }

    get hasRedirect() {
        return (
            this._redirectedURL != null ||
            this._dataSource.redirectedURL != undefined
        )
    }

    get currentRedirectedURL() {
        return (
            this._redirectedURL ||
            this._dataSource.redirectedURL
        )
    }

    // in KB/s
    get currentSpeed() {
        return this._speedSampler.lastSecondKBps
    }

    get loaderType() {
        return this._loader.type
    }

}